home *** CD-ROM | disk | FTP | other *** search
- Program LotusLOOK; (* A Program to read and partially decode LOTUS123 files *)
-
- (*
- The purpose of this example program is to provide the information needed
- to create and interpret 1-2-3 data files from within a Turbo Pascal
- program.
-
- This file contains the type definitions and procedures necessary to read
- and write Lotus 1-2-3 WKS files. The program will work with versions
- 1,1a and 2 of 1-2-3.
-
- The information in this program was developed from two sources; First,
- a handout which was distributed at the Lotus Developer's conference
- held by Lotus, and second, plain old trial and error. The information
- distributed at the conference gave the name of each type of record
- and the number of bytes normally contained by that record. Trial and
- error was used to determine how the information in the records was to
- be interpreted. In the case of some record types, no attempt has been
- made to interpret the data. For instance, the definition of graph
- records has been defined as an array of bytes without regard for
- the actual function of those bytes.
-
- A Lotus 1-2-3 File consists of a series of variable length records.
- The first two bytes of each record represent an integer which gives
- the record Type, the third and fourth bytes contain an integer which
- represents the number of bytes in the record (exclusive of the type
- and length bytes).
-
- Columns and Rows within the worksheet are numbered starting with 0.
- Thus, cell A1 in the spreadsheet is Cell[0,0] in the matrix, cell B2
- is Cell[1,0]. Cells are written to the file in sequence working across
- each row, i.e., all the cells in row 0 are written, then all cells in
- row 1, etc.
-
- Each record describing a cell contains a format byte, the column and
- row where it fits in the matrix and the contents of that cell, be it
- integer, floating point, text, or fomula. Cells containing dates
- are treated as integer cells. Text information is stored in the form
- of ASCII Z strings. Unlike Turbo strings where byte[0] gives the length
- of the string, ASCIIZ strings are arrays of characters with a character
- #0 representing the end of the string. The format byte consists of two
- 4 bit fields, one of which is used to flag the format type--Fixed, Currency,
- Date, etc. The other is used to denote the decimal places to be used, or
- the number of the date type. Experiment with files of your own creation,
- and you will be able to see the relationships.
-
- The basic 1-2-3 record types are:
-
- 0 : Beginning of File - 2 Data Bytes;
- 1 : End of File - 0 Data Bytes;
- 2 : Method of Calculation - 1 Data Byte;
- 3 : Calculation order - 1 Data Byte;
- 4 : Window Split Indicator - 1 Data Byte;
- 5 : Window Synchronization Indicator - 1 Data Byte;
- 6 : WorkSheet Dimensions - Number of Columns, Number of Rows : Integers;
- *7 : First Window Data - Varies depending on version number
- 8 : Column width overrides - Column number, Column width;
- A column width record exists for any column for which the
- user has established a width other than the default;
- *9 : Second Window Data - Varies depending on version number;
- 10 : Column Width Overrides for window two;
- 11 : Named Range : Title, Column,Row of upper corner, Column,Row of lower
- corner;
- 12 : Blank Cell : Format Byte, Column, Row; A cell with no contents
- 13 : Integer Cell : Format Byte, Column, Row, cell contents;
- 14 : Floating Point Cell : Format Byte, Column, Row, IEEE Real Number;
- 15 : Label Cell : Format Byte, Column, Row, Contents
- 16 : Formula Cell : Format Byte, Column, Row, Contents
- *24 : Data Table Range : 25 Bytes representing the coordinates of the range;
- *25 : Query Range : 25 bytes representing the coordinate;
- *26 : Print Range : Column and row of the upper and lower corners of the range
- *27 : Sort Range : Column and row of the upper and lower corners of the range
- *28 : Fill Range : Column and row of the upper and lower corners of the range
- *29 : Primary Sort Key : Coordinates of top and bottom of the range,
- Byte denoting ascending or descending sort
- *32 : Distribution range : Coordinates of the range;
- *35 : Secondary Sort Key : Coordinates of top and bottom of the range,
- Byte denoting ascending or descending sort
- 36 : Global Protection : Byte
- 37 : Footer : ASCII Z string for page footer;
- 38 : Header : ASCII Z string for page header;
- 39 : Setup String : ASCII Z string for printer setup;
- *40 : Print Margins : Left, Rigth Margin, Page Length, Top , Bottom Margin
- 41 : Global Label Alignment Character : Character (',",^);
- *42 : Borders : Coordinates of print borders;
- *45 : Current Graph Information : 437 Bytes;
- *46 : Named Graph Information : 453 Bytes;
- 47 : Iteration Count : Byte;
- 48 : Print Format Flag : Byte;
- 49 : Active Window Flag : Byte;
-
- * denotes definitions where the ordering of information has not yet been
- established.
-
- An "Empty File" (No data in the cells yet) as created by Lotus 1-2-3
- contains the following records:
-
- 0 - Begin File Information
- 6 - Spreadsheet Dimensions
- 47 - Iteration setting
- 2 - Calculation method
- 3 - Calculation order
- 4 - Split Window Flag
- 5 - Window Synchronization Flag
- 7 - Window 1 Data
- 24 - Data Table information
- 25 - Query Range information
- 26 - Print Range information
- 48 - Print format flag
- 28 - Data Fill range information
- 27 - Data Sort Range information
- 29 - Primary Sort Range
- 35 - Secondary Sort Range
- 32 - Distribution Range
- 36 - Protection Flag
- 37 - Footer
- 38 - Header
- 39 - Setup string
- 40 - Margins
- 41 - Global label alignment character
- 42 - Print borders
- 45 - Graph settings
-
- Although 1-2-3 places these records in an empty file, my tests have shown that
- a file containing just the BeginFile record, the Worksheet Dimensions record,
- then the various cells to be placed in the file and then the EndFile record
- will be loaded properly by 1-2-3. This appears to offer a more streamlined
- method of creating files which can be used by 1-2-3; plus it saves all the
- disk file space which is occupied by those default records.
-
- It appears that in the various range records the byte 255 is used to indicate
- that the range has not yet been set.
-
- In order to read a 1-2-3 file, one merely opens the file and starts reading
- the record type bytes, after the record type has been read the next two
- bytes will tell you how many data bytes there are in the record. Read the
- specified number of bytes into a buffer and then apply the proper interp-
- retation to those bytes. In order to facilitate this process a Pascal
- record with variant parts has been defined.
-
- The primary need of a Turbo programmer will be to transfer a data base
- or other data from a Turbo application to a 1-2-3 application. By writing
- directly to a 1-2-3 format file one can spare the user of the program
- the burden of dumping the data to a DIF or Text file and then using the
- 1-2-3 translate programs to place the data in a 1-2-3 worksheet file which,
- by the way, do not behave too well when text and numeric data are mixed.
-
- Preparing the Turbo generated 1-2-3 file requires that the file be
- filled with the default values for the records found in all files.
- Then the actual data base information would be written to the file
- one row (record) at a time, making the proper conversions from
- Turbo strings and turbo reals. Dates, of course, would have to be
- converted to integers if the application will call for manipulation
- of the dates within 1-2-3. Several programs in the Borland SIG DL1
- contain routines to convert dates to integers based on various starting
- dates.
-
- Comments, improvements, etc. can be addressed to:
- Gary Macker, CPA
- Compuserve ID 76314,2747
-
- *)
-
- Type
- String16 = String[16];
-
- ByteFile = File of Byte;
-
- ZType = Array[1..255] of Char; (* ASCII Z String *)
-
- AnyString = String[255];
-
- IEEERealType = Array[1..8] of byte;
-
- TurboRealType = Array[1..6] of byte;
-
- MantissaType = Array[1..5] of byte;
-
- BitType = (B0,B1,B2,B3,B4,B5,B6,B7);
-
- BitSetType = Set of B0..B7;
-
- WksDimType = Record
- UpLftCol : Integer; (* Home *)
- UpLftRow : Integer;
- LowRtCol : Integer; (* End Home *)
- LowRtRow : Integer
- End;
-
- WinDataType = Record
- Col : Integer;
- Width : Byte;
- End;
-
- RangeRecType = Record
- Name : Array[1..16] of Char; (* An ASCIIZ String *)
- Col1,Row1,Col2,Row2 : Integer;
- End;
-
- BlankCellType = Record
- Format : BitSetType;
- Col,Row : Integer;
- End;
-
- IntegerCellType = Record
- Format : BitSetType;
- Col,Row,Num : Integer;
- End;
-
- RealCellType = Record
- Format : BitSetType;
- Col,Row : Integer;
- Num : IEEERealType;
- End;
-
- LabelCellType = Record
- Format : BitSetType;
- Col,Row : Integer;
- CellLabel : ZType;
- End;
-
- FormulaCellType = Record
- Format : BitSetType;
- Col,Row : Integer;
- LastValue : IEEERealType;
- Formula : Array[1..255] of Byte;
- End;
-
- LotusRecType = Record
- RecType : Integer;
- RecSize : Integer;
- Case Integer of
- 0 : (Flag1,Flag2 : Byte);
- 2 : (CalcMethod : Byte);
- 3 : (CalcOrder : Byte);
- 4 : (SplitWindow : Byte);
- 5 : (WindowSynch : Byte);
- 6 : (WksDim : WksDimType);
- 7 : (FirstWindowFlags : Array[1..32] of Byte); (* 31 bytes in Ver 1,1A; 32 in Ver 2 *)
- 8 : (WinOneData : WinDataType);
- 9 : (SecondWindowFlags : Array[1..32] of Byte); (* 31 bytes in Ver 1,1A; 32 in Ver 2 *)
- 10 : (WinTwoData : WinDataType);
- 11 : (RangeRec : RangeRecType);
- 12 : (BlankCell : BlankCellType);
- 13 : (IntegerCell : IntegerCellType);
- 14 : (RealCell : RealCellType);
- 15 : (LabelCell : LabelCellType);
- 16 : (FormulaCell : FormulaCellType);
- 24 : (TableData : Array[1..25] of Byte);
- 25 : (QueryRangeData : Array[1..25] of Byte);
- 26 : (PrintRangeCol1,PrintRangeRow1,PrintRangeCol2,PrintRangeRow2 : Integer);
- 27 : (SortRangeData : Array[1..4] of Integer);
- 28 : (FillRangeData : Array[1..4] of Integer);
- 29 : (PrimSortData : Array[1..9] of Byte);
- 32 : (DistRangeData : Array[1..16] of Byte);
- 35 : (SecSortData : Array[1..9] of Byte);
- 36 : (ProtectOn : Byte);
- 37 : (Footer : Array[1..242] of Char);
- 38 : (Header : Array[1..242] of Char);
- 39 : (SetupString : Array[1..40] of Char);
- 40 : (LeftM,RightM,PageL,BottM,TopM : Integer);
- 41 : (LabelPrefix : Char);
- 42 : (BorderRange : Array[1..8] of Integer);
- 45 : (GraphData : Array[1..437] of Integer);
- 46 : (NamedGraphData : Array[1..453] of Integer);
- 47 : (Iteration : Byte);
- 48 : (PrintFormat : Byte);
- 49 : (ActiveWindow : Byte);
- End;
-
- Var
- LotusRec : LotusRecType;
- RecordBuffer : Array[1..455] of Byte Absolute LotusRec;
- F : ByteFile;
- FileName : String[60];
- C,Ch : Char;
-
- Const
- RecNames : Array[0..$31] of String[25] = (
- 'Begin File',
- 'End File',
- 'Calculation Method',
- 'Calculation Order',
- 'Split Window',
- 'Window Synch',
- 'Worksheet Dimensions',
- 'First Window',
- 'Col Width Overrides/1',
- 'Second Widow',
- 'Col Width Overrides/2',
- 'Named Range',
- 'Blank Cell',
- 'Integer Cell',
- 'Floating Point Cell',
- 'Label Cell',
- 'Formula Cell',
- '',
- '',
- '',
- '',
- '',
- '',
- '',
- 'Table Range',
- 'Query Range',
- 'Print Range',
- 'Sort Range',
- 'Fill Range',
- 'Primary Key',
- '',
- '',
- 'Distribution Range',
- '',
- '',
- 'Secondary Key',
- 'Global Protection',
- 'Print Footer',
- 'Print Header',
- 'Setup String',
- 'Print Margins',
- 'Global Label',
- 'Print Borders',
- '',
- '',
- 'Current Graph Settings',
- 'Named Graph',
- 'Iteration Count',
- 'Print Format Mode',
- 'Active Window');
-
- Zero : Byte = 0;
- One : Byte = 1;
- two : Byte = 2;
- Three : Byte = 3;
- Four : Byte = 4;
- Five : Byte = 5;
- Six : Byte = 6;
- Seven : Byte = 7;
- Eight : Byte = 8;
- Nine : Byte = 9;
- Ten : Byte = 10;
- Eleven : Byte = 11;
- Twelve : Byte = 12;
- Thirteen : Byte = 13;
- Fourteen : Byte = 14;
-
- Function IeeeToTurbo(long : IEEERealType) : Real;
-
- (*
- * This function was adapted from information supplied by Borland for use
- * in reading and writing Reflex data files. Note that the routine as
- * set forth in that file did not handle the conversion properly when
- * the number was 0.00. It added the bias to the exponent, which then
- * made the converted number non-zero. A zero exponent (absolute zero,
- * not zero after correcting for the sign bit) denotes 0.00 in both
- * the IEEE and Turbo 6 byte formats. A secondary result of the way
- * in which I have altered the routine is that if the exponent falls
- * outside the possible range for a Turbo real, the number will be
- * converted to 0.00. You may want to institute different error
- * handling in your application
- *)
-
- Var
- R : Real;
- I : Integer;
- E : byte;
- T : TurboRealType Absolute R;
- Sign : Byte;
-
- Begin
- FillChar(R,SizeOf(R),0);
- I := (long[8] and $7f) shl 4;
- I := I or ((long[7] and $f0) shr 4);
- if (I < 985) or (I > 1061) then T[1] := 0
- Else
- Begin
- I := I - 1023;
- T[1] := I + $81;
- End;
- Sign := long[8] and $80;
- T[6] := sign + ((long[7] and $0f) shl 3) or ((long[6] and $e0) shr 5);
- for I := 5 downto 2 do
- t[I] := ((long[I+1] and $1f) shl 3) or ((long[I] and $e0) shr 5);
- If T[1] <> 0 Then IEEEtoTurbo := R Else R := 0;
- End;
-
- Procedure TurboToIEEE(Var TN : Real; Var IEEE : IEEERealType);
- Var
- TR : TurboRealType Absolute TN;
- E : Integer;
- Sign : Byte;
- Begin
- FillChar(IEEE,SizeOf(IEEE),0); (* Start with a clean slate *)
- If TR[1] <> 0 Then (* Zero means 0.00 *)
- Begin
- Sign := TR[6] and $80;
- E := TR[1] - $81;
- E := E + 1023;
- IEEE[8] := Sign + (E Shr 4);
- IEEE[7] := E Shl 4;
- TR[6] := TR[6] or $80;
- IEEE[7] := IEEE[7] + ((TR[6] and $78) Shr 3);
- IEEE[6] := ((TR[6] and $07) Shl 5) + ((TR[5] and $F8) Shr 3);
- IEEE[5] := ((TR[5] and $07) Shl 5) + ((TR[4] and $F8) Shr 3);
- IEEE[4] := ((TR[4] and $07) Shl 5) + ((TR[3] and $F8) Shr 3);
- IEEE[3] := ((TR[3] and $07) Shl 5) + ((TR[2] and $F8) Shr 3);
- IEEE[2] := ((TR[2] and $07) Shl 5) + (($0 and $F8) Shr 3);
- IEEE[1] := 0; (* Least significant bits made 0 *)
- End;
- End;
-
- Procedure GetName;
- Var
- I : Integer;
- Begin
- ClrScr;
- Write('ENTER THE NAME OF THE FILE YOU WANT TO DECODE :');
- ReadLn(FileName);
- If FileName = '' Then Halt;
- For I := 1 to Length(FileName) Do FileName[I] := UpCase(FileName[I]);
- If Pos('.',FileName) = 0 Then FileName := FileName +'.WKS';
- End;
-
- Function OpenFile(FN : AnyString) : Boolean;
- Var
- B : Boolean;
- Begin
- Assign(F,FN);
- {$I-}
- Reset(F);
- B := (IOResult = 0);
- {$I+}
- If Not B Then Close(F);
- OpenFile := B;
- End;
-
- Procedure ReadLotusRec(Var F : ByteFile); (* Assumes that the file pointer is at the start of a record *)
- Var
- I : Integer;
- Begin
- For I := 1 to 4 Do Read(F,RecordBuffer[I]);
- For I := 5 to LotusRec.RecSize+4 Do Read(F,RecordBuffer[I]);
- End;
-
- Procedure WriteZ(Var Z);
- Var
- ZString : ZType Absolute Z;
- I : Byte;
- Begin
- I := 1;
- While ZString[I] > #0 Do
- Begin
- Write(ZString[I]);
- I := Succ(I);
- End;
- End;
-
- Function BitRep(B : BitSetType) : String16;
- Var
- S : String16;
- Bit : BitType;
- Begin
- S := '';
- For Bit := B7 downto B0 Do If Bit in B Then S := S +'1 ' Else S := S + '0 ';
- BitRep := S;
- End;
-
- Procedure PrintRec;
- Var
- I : Integer;
- R : Real;
- Begin
- WriteLn('Record Type = ',LotusRec.RecType,' ',RecNames[LotusRec.RecType]);
- WriteLn('Size in Bytes = ',LotusRec.RecSize);
- WriteLn('Record Contents:');
- With LotusRec Do Case RecType of
- 0 : Write(Flag1:4,Flag2:4);
- 1 : Begin End;
- 2 : Write(CalcMethod);
- 3 : Write(CalcOrder);
- 4 : Write(SplitWindow);
- 5 : Write(WindowSynch);
- 6 : With WksDim Do Begin
- Write(UpLftCol:4, UpLftRow:4, LowRtCol:4, LowRtRow:4);
- End;
- 7 : For I := 1 to RecSize Do Write(FirstWindowFlags[I]:4);
- 8 : With WinOneData Do Write('Column ',Col,' is ',Width,' Spaces Wide');
- 9 : For I := 1 to RecSize Do Write(SecondWindowFlags[I]:4);
- 10 : With WinTwoData Do Write('Column ',Col,' is ',Width,' Spaces Wide');
- 11 : With RangeRec Do Begin
- WriteZ(Name);
- WriteLn;
- Write('Column 1 ',Col1,' Row 1 ',Row1,' Column 2 ',Col2,' Row 2 ',Row2);
- End;
- 12 : With BlankCell Do Begin
- WriteLn('Format Byte = ',BitRep(Format));
- Write('Column ',Col,' Row ',Row);
- End;
- 13 : With IntegerCell Do Begin
- WriteLn('Format Byte = ',BitRep(Format));
- WriteLn('Column ',Col,' Row ',Row);
- Write('Integer Value is ',Num);
- End;
- 14 : With RealCell Do Begin
- WriteLn('Format Byte = ',BitRep(Format));
- WriteLn('Column ',Col,' Row ',Row);
- R := IEEEtoTurbo(Num);
- Write('Cell Value is ',R:15:2);
- End;
- 15 : With LabelCell Do Begin
- WriteLn('Format Byte = ',BitRep(Format));
- WriteLn('Column ',Col,' Row ',Row);
- WriteZ(CellLabel);
- End;
- 16 : With FormulaCell Do Begin
- WriteLn(BitRep(Format));
- WriteLn('Column ',Col,' Row ',Row);
- I := 1;
- R := IEEEtoTurbo(LastValue);
- WriteLn('Last Computed Value is ',R:15:2);
- For I := 1 to (LotusRec.RecSize - 8) Do Write(Formula[I]:4);
- End;
- 24 : For I := 1 to RecSize Do Write(TableData[I]:4);
- 25 : For I := 1 to RecSize Do Write(QueryRangeData[I]:4);
- 26 : Write(PrintRangeCol1:4,PrintRangeRow1:4,PrintRangeCol2:4,PrintRangeRow2:4);
- 27 : For I := 1 to 4 Do Write(SortRangeData[I]:4);
- 28 : For I := 1 to 8 Do Write(FillRangeData[I]:4);
- 29 : For I := 1 to RecSize Do Write(PrimSortData[I]:4);
- 32 : For I := 1 to RecSize Do Write(DistRangeData[I]:4);
- 35 : For I := 1 to 9 Do Write(SecSortData[I]:4);
- 36 : Write(ProtectOn);
- 37 : WriteZ(Footer);
- 38 : WriteZ(Header);
- 39 : WriteZ(SetupString);
- 40 : Write('Left ',LeftM,' Right ',RightM,' Top ',TopM,' Bottom ',BottM,' Page Length ',PageL);
- 41 : Write(LabelPrefix);
- 42 : For I := 1 to 8 Do Write(BorderRange[I]:4);
- 45 : For I := 1 to 437 Do Write(GraphData[I]:4);
- 46 : For I := 1 to 453 Do Write(NamedGraphData[I]:4);
- 47 : Write(Iteration);
- 48 : Write(PrintFormat);
- 49 : Write(ActiveWindow);
- End;
- WriteLn;
- End;
-
- Procedure WriteBeginFile(Var F : ByteFile);
- Begin
- Write(F,Zero,Zero,Two,Zero,Four,Four);
- (* Record Type 0, Record Size 2, ??? 4, 4 *)
- End;
-
- Procedure WriteWksSize(Var F : ByteFile; Cols,Rows : Integer);
- Var
- B : Byte;
- Begin
- Write(F,Six,Zero,Eight,Zero,Zero,Zero,Zero,Zero);
- B := Lo(Cols);
- Write(F,B);
- B := Hi(Cols);
- Write(F,B);
- B := Lo(Rows);
- Write(F,B);
- B := Hi(Rows);
- Write(F,B);
- End;
-
- Procedure WriteRealCell(Var F : ByteFile; FormatByte : Byte; Col,Row : Integer; Value : Real);
- Var
- B : Byte;
- IE : IEEERealType;
-
- Begin
- Write(F,Fourteen,Zero,Thirteen,Zero,FormatByte);
- B := Lo(Col);
- Write(F,B);
- B := Hi(Col);
- Write(F,B);
- B := Lo(Row);
- Write(F,B);
- B := Hi(Row);
- Write(F,B);
- TurboToIEEE(Value,LotusRec.RealCell.Num);
- For B := 1 to 8 Do Write(F,LotusRec.RealCell.Num[B]);
- End;
-
- Procedure WriteIntegerCell(Var F : ByteFile; FormatByte : Byte; Col,Row,Value : Integer);
- Var
- B : Byte;
- Begin
- Write(F,Thirteen,Zero,Seven,Zero,FormatByte);
- B := Lo(Col);
- Write(F,B);
- B := Hi(Col);
- Write(F,B);
- B := Lo(Row);
- Write(F,B);
- B := Hi(Row);
- Write(F,B);
- B := Lo(Value);
- Write(F,B);
- B := Hi(Value);
- Write(F,B);
- End;
-
- Procedure EndLotusFile(Var F : ByteFile);
- Begin
- Write(F,One,Zero,Zero,Zero);
- Close(F);
- End;
-
- Begin
- Repeat GetName Until OpenFile(FileName);
- Repeat
- ReadLotusRec(F);
- PrintRec;
- Read(kbd,C);
- Until LotusRec.RecType = 1;
- Close(F);
- Writeln('Create a sample WKS file... ? Y/N');
- Write('It will be called TEST2.WKS');
- Read(Ch);
- If UpCase(CH) = 'Y' Then
- Begin
- Assign(F,'TEST2.WKS');
- ReWrite(F);
- WriteBeginFile(F);
- WriteWksSize(F,2,0);
- WriteIntegerCell(F,255,0,0,1);
- WriteIntegerCell(F,255,1,0,2);
- WriteRealCell(F,255,2,0,1.123);
- EndLotusFile(F);
- End;
- End.